﻿#include "f5g006_api.h"
#include "f5g006_ds.h"
#include "timeout_watch.h"
#include "logutils.h"

static const uint8_t C_STX = 0xF2;
static const uint8_t C_ACK = 0x06;
static const uint8_t C_NAK = 0x15;
static const uint8_t C_IDN = 'C';


#define WERROR(e)   ((((e)[0])<<8)+(e)[1])

const uint16_t _crc_tab[] =
{
    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5,0x60c6, 0x70e7,
    0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
    0x1231, 0x0210,0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
    0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c,0xf3ff, 0xe3de,
    0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
    0xa56a, 0xb54b,0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
    0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6,0x5695, 0x46b4,
    0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
    0x48c4, 0x58e5,0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
    0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969,0xa90a, 0xb92b,
    0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
    0xdbfd, 0xcbdc,0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
    0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03,0x0c60, 0x1c41,
    0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
    0x7e97, 0x6eb6,0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
    0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a,0x9f59, 0x8f78,
    0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
    0x1080, 0x00a1,0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
    0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c,0xe37f, 0xf35e,
    0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
    0xb5ea, 0xa5cb,0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
    0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447,0x5424, 0x4405,
    0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
    0x26d3, 0x36f2,0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
    0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9,0xb98a, 0xa9ab,
    0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
    0xcb7d, 0xdb5c,0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
    0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0,0x2ab3, 0x3a92,
    0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
    0x7c26, 0x6c07,0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
    0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba,0x8fd9, 0x9ff8,
    0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};


static uint16_t crc_ccitt_calc(uint16_t init_val, const uint8_t* buf, int len)
{
    uint8_t data;
    uint16_t remainder = init_val;

    for (int i = 0; i < len; i++) {
        data = buf[i] ^ (remainder >> 8);
        remainder = _crc_tab[data] ^ (remainder << 8);
    }

    return remainder;
}


int tresultToApiError(TResult result)
{
    switch (result) {
    case TResult::NoError: return 0;
    case TResult::DevNotFound: return F5G006_E_DEV_NOT_FOUND;
    case TResult::Access: return F5G006_E_ACCESS;
    case TResult::ConnLost: return F5G006_E_CONN_LOST;
    case TResult::IoError: return F5G006_E_IO_ERROR;
    case TResult::TimedOut: return F5G006_E_TIMEOUT;
    case TResult::OtherError: return F5G006_E_UNKNOWN_ERROR;
    default: return F5G006_E_UNKNOWN_ERROR;
    }
}


//
// F5G006_DeviceService
//

F5G006_DeviceService::F5G006_DeviceService(std::unique_ptr<Transport> transport)
    : m_transport(std::move(transport))
{
}


F5G006_DeviceService::~F5G006_DeviceService()
{
    m_transport.reset();
}


int F5G006_DeviceService::doCommand(uint8_t CC1, uint8_t CC2, long timeoutVal)
{
    return doCommand(CC1, CC2, nullptr, 0, timeoutVal);
}


int F5G006_DeviceService::doCommand(uint8_t CC1, uint8_t CC2, const void* pbCMP, int CMPSize, long timeoutVal)
{
    const int MaxRetryTimes = 2;

    uint8_t ans;
    int ret, msgLen, retryCnt = 0;
    TimeoutWatch tw(timeoutVal);

    ret = initCommandMessage(CC1, CC2, pbCMP, CMPSize, msgLen);
    if (ret != 0)
        return ret;

L_RESTART:
    m_transport->discardIoBuffers();

    ret = tresultToApiError(m_transport->write(m_msgBuf, msgLen, tw.remainingTime()));
    if (ret != 0)
        return ret;
    
    ret = waitForACK(1000, ans);
    log_info("120 ret %d",ret);
    if (ret != 0)
        return ret;

    if (ans != C_ACK) { // NAK
        if (++retryCnt <= MaxRetryTimes) {
            log_info("NAK! 重试...");
            goto L_RESTART;
        }

        log_error("等待接收 ACK 期间，接收到 NAK!");
        return F5G006_E_RECEPTION;
    }

    ret = recvResponse(tw.remainingTime(), msgLen);
    log_info("137 ret %d", ret);
    if (ret != 0)
        return ret;
    log_info("140");
    tresultToApiError(m_transport->write(&C_ACK, 1, tw.remainingTime()));
    log_info("142");
    return checkLastResponse(CC1, CC2, msgLen);
}


int F5G006_DeviceService::initCommandMessage(uint8_t CC1, uint8_t CC2, const void* pbCMP, int CMPSize, int& msgLen)
{
    int textLen = 3;
    if (pbCMP != nullptr && CMPSize > 0) {
        textLen += CMPSize;
        if (textLen + 5 > MAX_MSG_SIZE) {
            log_error("内部缓冲区过小");
            return F5G006_E_INTERNAL_ERROR;
        }
    }

    msgLen = 0;
    m_msgBuf[msgLen++] = C_STX;
    m_msgBuf[msgLen++] = (uint8_t)(textLen >> 8);
    m_msgBuf[msgLen++] = (uint8_t)(textLen);
    m_msgBuf[msgLen++] = C_IDN;
    m_msgBuf[msgLen++] = CC1;
    m_msgBuf[msgLen++] = CC2;

    if (pbCMP != nullptr && CMPSize > 0) {
        memcpy(&m_msgBuf[msgLen], (uint8_t*)pbCMP, CMPSize);
        msgLen += CMPSize;
    }

    uint16_t crcc = crc_ccitt_calc(0, m_msgBuf, msgLen);
    m_msgBuf[msgLen++] = (uint8_t)(crcc >> 8);
    m_msgBuf[msgLen++] = (uint8_t)(crcc);

    return 0;
}


int F5G006_DeviceService::waitForACK(long timeoutVal, uint8_t& bAns)
{
    int ret;
    TimeoutWatch tw(timeoutVal);

    do {
        log_info("185 tw.remainingTime() %d", tw.remainingTime());
        ret = tresultToApiError(m_transport->read(&bAns, 1, tw.remainingTime()));
        log_info("187 ret %d tw.remainingTime() is %d", ret, tw.remainingTime());
        if (ret != 0) {
            if (ret == F5G006_E_TIMEOUT) {
                return F5G006_E_NO_ACK;
            }
            return ret;
        }

        if (bAns == C_ACK || bAns == C_NAK )
            return 0;

    } while (1);
}


int F5G006_DeviceService::recvResponse(long timeoutVal, int& msgLen)
{
    int ret, textLen;
    TimeoutWatch tw(timeoutVal);

    /* 读消息头STX(1字节) */
    do {
        ret = tresultToApiError(m_transport->read(&m_msgBuf[0], 1, tw.remainingTime()));
        if (ret != 0)
            return ret;
        if (m_msgBuf[0] == C_STX)
            break;
    } while (1);

    /* 读长度字段(2字节) */
    ret = tresultToApiError(m_transport->read(&m_msgBuf[1], 2, tw.remainingTime()));
    if (ret != 0)
        return ret;

    /* 检查长度是否正确 */
    textLen = (m_msgBuf[1] << 8) + m_msgBuf[2];    // 文本长度
    if (textLen + 5 < MIN_MSG_SIZE) {
        log_error("响应消息的长度不正确. 错误数据: %s",
                  bytesToHexString(m_msgBuf, 3)->c_str());
        return F5G006_E_RECEPTION;
    }

    if (textLen + 5 > MAX_MSG_SIZE) {
        log_error("用于保存响应消息的缓冲区大小. 已读取的消息数据: %s",
                  bytesToHexString(m_msgBuf, 3)->c_str());
        return F5G006_E_INTERNAL_ERROR;
    }

    /* 读应答数据和CRCC */
    ret = tresultToApiError(m_transport->read(&m_msgBuf[3], textLen + 2, tw.remainingTime()));
    if (ret != 0)
        return ret;

    msgLen = textLen + 5;

    return 0;
}


int F5G006_DeviceService::checkLastResponse(uint8_t CC1, uint8_t CC2, int msgLen)
{
    /* 检查CRC是否正确 */
    uint16_t crcc1 = (m_msgBuf[msgLen - 2] << 8) + m_msgBuf[msgLen - 1];
    uint16_t crcc2 = crc_ccitt_calc(0, m_msgBuf, msgLen - 2);
    if (crcc1 != crcc2) {
        log_error("CRC 不正确. 错误数据: %s",
            bytesToHexString(m_msgBuf, msgLen)->c_str());
        return F5G006_E_RECEPTION;
    }

    if (m_msgBuf[3] == 'P') {
        if (msgLen < MIN_P_MSG_SIZE) {
            log_error("响应消息的长度不正确. 错误数据: %s",
                bytesToHexString(m_msgBuf, msgLen)->c_str());
            return F5G006_E_RECEPTION;
        }
        m_pLastRDT = &m_msgBuf[9];
        m_lastRDTSize = msgLen - MIN_P_MSG_SIZE;
        return 0;
    }

    if (m_msgBuf[3] != 'N') {
        log_error("响应消息的格式不正确. 缺少 \'N\' or \'P\'. 错误数据: %s",
            bytesToHexString(m_msgBuf, msgLen)->c_str());
        return F5G006_E_RECEPTION;
    }

    int ret;
    switch (WERROR(&m_msgBuf[6])) {
    case WERROR("30"): ret = F5G006_E_POWER_DOWN; break;
    case WERROR("A0"): ret = F5G006_E_SUPPLY_BIN_EMPTY; break;
    case WERROR("A1"): ret = F5G006_E_NO_SUPPLY_BIN; break;
    case WERROR("A3"): ret = F5G006_E_NO_SUPPLY_BIN; break;
    case WERROR("A5"):
    case WERROR("A6"):
    case WERROR("A7"): ret = F5G006_E_HARDWARE_ERROR; break;
    case WERROR("AB"): ret = F5G006_E_NO_RETAIN_BIN; break;
    case WERROR("AC"): ret = F5G006_E_RETAIN_BIN_FULL; break;
    default: ret = F5G006_E_FAILURE; break;
    }

    log_error("命令 %02X%02X (%c%c) 失败: %02X%02X%02X (%c%c%c)",
              CC1, CC2, CC1, CC2,
              0x4E, m_msgBuf[6], m_msgBuf[7],
              0x4E, m_msgBuf[6], m_msgBuf[7]);

    return ret;
}
